/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.module.apikit.uri;

import java.util.Hashtable;
import java.util.Map;

/**
 * A factory for URI tokens.
 * <p/>
 * <p>Tokens can be instantiated from an expression which is specific to each token.
 *
 * @author Christophe Lauret
 * @version 6 November 2009
 * @see TokenLiteral
 * @see TokenVariable
 * @see TokenOperatorD3
 * @see TokenOperatorPS
 */
public class TokenFactory
{

    /**
     * A syntax to use for creating tokens.
     *
     * @author Christophe Lauret
     * @version 6 November 2009
     */
    public enum Syntax
    {

        /**
         * Use the syntax defined in Draft 3 of the URI templates.
         */
        DRAFT3
                {
                    @Override
                    protected Token newExpansion(String exp)
                    {
                        // an operator as defined in Draft 3
                        if (exp.indexOf('|') >= 0)
                        {
                            return TokenOperatorD3.parse(exp);
                            // assume a variable
                        }
                        else
                        {
                            return new TokenVariable(Variable.parse(exp));
                        }
                    }

                },

        /**
         * Use a syntax currently used in PageSeeder and based on what was suggested
         * by Roy T Fielding on the W3C URI listing in October 2008.
         */
        PAGESEEDER
                {
                    @Override
                    protected Token newExpansion(String exp)
                    {
                        // possibly Roy Fielding's operators
                        if (!Character.isLetter(exp.charAt(0)) && !Character.isDigit(exp.charAt(0)))
                        {
                            return TokenOperatorPS.parse(exp);
                            // assume a variable
                        }
                        else
                        {
                            return new TokenVariable(Variable.parse(exp));
                        }
                    }

                },

        /**
         * Use a syntax based on the draft as of 29 October 2009.
         */
        DRAFTX
                {
                    @Override
                    protected Token newExpansion(String exp)
                    {
                        // possibly Roy Fielding's operators
                        if (!Character.isLetter(exp.charAt(0)) && !Character.isDigit(exp.charAt(0)))
                        {
                            return TokenOperatorDX.parse(exp);
                            // maybe a collection
                        }
                        else if (exp.indexOf(',') >= 0)
                        {
                            return TokenOperatorDX.parse(exp);
                            // assume a variable
                        }
                        else
                        {
                            return new TokenVariable(Variable.parse(exp));
                        }
                    }

                };

        /**
         * Generates a template expansion token corresponding to the specified expression.
         *
         * @param exp The expression within the curly brackets {}.
         * @return The corresponding token instance.
         * @throws URITemplateSyntaxException If the expression could not be parsed as a valid token.
         */
        protected abstract Token newExpansion(String exp);

    }

    /**
     * Factories for reuse.
     */
    private static final Map<Syntax, TokenFactory> FACTORIES = new Hashtable<Syntax, TokenFactory>();

    static
    {
        for (Syntax syntax : Syntax.values())
        {
            FACTORIES.put(syntax, new TokenFactory(syntax));
        }
    }

    /**
     * The URI template syntax to use for generating tokens.
     */
    private Syntax _syntax;

    /**
     * Prevents creation of instances.
     *
     * @param syntax The URI template syntax to use for generating tokens.
     */
    private TokenFactory(Syntax syntax)
    {
        this._syntax = syntax;
    }

    /**
     * Generates the token corresponding to the specified expression.
     *
     * @param exp The expression.
     * @return The corresponding token instance.
     * @throws URITemplateSyntaxException If the expression could not be parsed as a valid token.
     */
    public Token newToken(String exp)
    {
        // no expression: no token!
        if (exp == null || exp.length() == 0)
        {
            return null;
        }
        // intercept the wild card
        if ("*".equals(exp))
        {
            return newWildcard();
        }
        // too short to be anything but a literal
        int len = exp.length();
        if (len < 2)
        {
            return new TokenLiteral(exp);
        }
        // a template expansion token
        if (exp.charAt(0) == '{' && exp.charAt(len - 1) == '}')
        {
            // defer to the underlying syntax
            return this._syntax.newExpansion(exp.substring(1, len - 1));
        }
        // a literal text token
        return new TokenLiteral(exp);
    }

    /**
     * Generates the token corresponding to the specified expression.
     *
     * @param exp The expression.
     * @return The corresponding token instance.
     * @throws URITemplateSyntaxException If the expression could not be parsed as a valid token.
     */
    public static Token newToken(String exp, Syntax syntax)
    {
        TokenFactory factory = getInstance(syntax);
        return factory.newToken(exp);
    }

    /**
     * Creates a new 'wildcard' token for legacy purposes.
     * <p/>
     * <p>This is used for conventional URI patterns which have been implemented using "*".
     *
     * @return A new 'wildcard' token.
     */
    private static final Token newWildcard()
    {
        return new TokenOperatorPS(TokenOperatorPS.Operator.URI_INSERT, new Variable(Variable.Reserved.WILDCARD));
    }

    /**
     * Returns a token factory instance using the default syntax (DRAFTX).
     *
     * @return a token factory instance using the default syntax (DRAFTX).
     */
    public static TokenFactory getInstance()
    {
        return FACTORIES.get(Syntax.DRAFTX);
    }

    /**
     * Returns a token factory instance.
     *
     * @return a token factory instance.
     */
    public static TokenFactory getInstance(Syntax syntax)
    {
        return new TokenFactory(syntax);
    }

}
